import type { MapInfo, NpcInfo, MonsterInfo, PlayerInfo } from './type'
import {
  Application,
  Graphics,
  Assets,
  Sprite,
  Container,
  Ticker,
  Rectangle,
  Texture,
  Text,
  type ContainerChild,
} from 'pixi.js'
import characterImage from '@/assets/character.png'

Assets.add({ alias: 'character', src: characterImage })

interface Options {
  container: HTMLElement
  mapInfo: MapInfo
  npcInfo?: NpcInfo[]
  monsterInfo?: MonsterInfo[]
  playerInfo?: PlayerInfo[]
}

export class XXGame {
  app: Application
  cellSize: number
  container: HTMLElement
  mapInfo: MapInfo
  npcInfo: NpcInfo[]
  npcContainer: Container
  monsterInfo: MonsterInfo[]
  monsterContainer: Container
  playerInfo: PlayerInfo[]
  playerContainer: Container
  characterTexture: any

  constructor(options: Options) {
    this.container = options.container
    this.mapInfo = options.mapInfo
    this.npcInfo = options.npcInfo
    this.npcContainer = new Container()
    this.monsterInfo = options.monsterInfo || []
    this.monsterContainer = new Container()
    this.playerInfo = options.playerInfo || []
    this.playerContainer = new Container()
    this.cellSize = 54
  }

  async init() {
    const app = new Application()
    this.app = app

    await app.init({
      width: this.mapInfo.width * this.cellSize,
      height: this.mapInfo.height * this.cellSize,
      backgroundColor: '#F1ECE1',
    })
    this.container.appendChild(app.canvas)

    app.stage.addChildAt(this.npcContainer, 0)
    app.stage.addChildAt(this.monsterContainer, 1)
    app.stage.addChildAt(this.playerContainer, 2)

    this.characterTexture = await Assets.load('character')
    this.npcInfo.map((item) => this.generateNPC(item))
    this.monsterInfo.map((item) => this.generateMonster(item))
    this.playerInfo.map((item) => this.generatePlayer(item))
  }

  generateNPC(npc: NpcInfo) {
    const typeIcons = {
      n1: { x: 9, y: 4 },
      n2: { x: 1, y: 1 },
      n3: { x: 3, y: 0 },
    }
    const iconSize = 54
    const npcTexture = new Texture({
      source: this.characterTexture,
      frame: new Rectangle(
        typeIcons[npc.type].x * iconSize,
        typeIcons[npc.type].y * iconSize,
        iconSize,
        iconSize
      ),
      orig: new Rectangle(0, 0, this.cellSize, this.cellSize),
    })
    const sprite = new Sprite(npcTexture)

    const container = new Container()
    const x = npc.x * this.cellSize
    const y = npc.y * this.cellSize
    container.position.set(x, y)
    container.addChild(sprite)

    this.npcContainer.addChild(container)
  }

  generateMonster(monster: MonsterInfo) {
    const container = new Container()
    const x = monster.x * this.cellSize
    const y = monster.y * this.cellSize
    container.label = monster.id
    container.position.set(x, y)
    this.monsterContainer.addChild(container)

    const typeIcons = {
      m1: { x: 3, y: 4 },
      m2: { x: 2, y: 3 },
      m3: { x: 4, y: 3 },
      m4: { x: 5, y: 2 },
    }
    const iconSize = 54
    const texture = new Texture({
      source: this.characterTexture,
      frame: new Rectangle(
        typeIcons[monster.type].x * iconSize,
        typeIcons[monster.type].y * iconSize,
        iconSize,
        iconSize
      ),
      orig: new Rectangle(0, 0, this.cellSize, this.cellSize),
    })

    const sprite = new Sprite(texture)
    const hp = this.generateHp({ pct: monster.rHp / monster.hp })
    const name = this.generateNickName({ name: monster.name, level: monster.level })
    container.addChild(sprite, hp, name)
  }

  generatePlayer(player: PlayerInfo) {
    const container = new Container()
    const x = player.x * this.cellSize
    const y = player.y * this.cellSize
    container.label = player.id
    container.position.set(x, y)
    this.playerContainer.addChild(container)

    const typeIcons = {
      p1: { x: 6, y: 4 },
      p2: { x: 0, y: 4 },
    }
    const iconSize = 54
    const playerTexture = new Texture({
      source: this.characterTexture,
      frame: new Rectangle(
        typeIcons[player.type].x * iconSize,
        typeIcons[player.type].y * iconSize,
        iconSize,
        iconSize
      ),
      orig: new Rectangle(0, 0, this.cellSize, this.cellSize),
    })

    const sprite = new Sprite(playerTexture)
    const hp = this.generateHp({ pct: player.rHp / player.hp, color: '#00A170' })
    const name = this.generateNickName({ name: player.name })
    container.addChild(sprite, hp, name)
  }

  moveContainer(
    target: ContainerChild,
    start: { x: number; y: number },
    end: { x: number; y: number },
    time = 1000
  ) {
    const ticker = new Ticker()
    const cellSize = this.cellSize
    let progress = 0
    ticker.add(() => {
      // 每帧进步1 / 60
      progress += 1 / (60 * (time / 1000))

      target.x = start.x * cellSize + (end.x - start.x) * cellSize * progress
      target.y = start.y * cellSize + (end.y - start.y) * cellSize * progress

      if (progress >= 1) {
        ticker.stop()
      }
    })
    ticker.start()
  }

  generateHp(options: { pct: number; width?: number; x?: number; y?: number; color?: string }) {
    const container = new Container()
    container.position.set(options.x ?? 2, options.y ?? 2)

    const width = options.width ?? this.cellSize - 4
    const hp = new Graphics()
    hp.label = 'hp'
    hp.fill(options.color ?? '#FF0000')
    hp.rect(0, 0, width * options.pct, 4)
    hp.fill()

    const hpbg = new Graphics()
    hpbg.label = 'hpbg'
    hpbg.fill('#000')
    hpbg.rect(0, 0, width, 4)
    hpbg.fill()

    container.addChild(hpbg, hp)
    return container
  }

  updateHp(target: ContainerChild, pct: number) {
    const hp = this.findNode(target.children, 'hp')
    const hpbg = this.findNode(target.children, 'hpbg')
    hp.width = hpbg.width * pct
  }

  findNode(nodes: ContainerChild[], label: string) {
    for (let i = 0; i < nodes.length; i++) {
      if (nodes[i].label === label) {
        return nodes[i]
      } else if (Array.isArray(nodes[i].children) && nodes[i].children.length > 0) {
        let result = this.findNode(nodes[i].children, label)
        if (result) return result
      }
    }
    return null
  }

  generateNickName(options: { name: string; level?: number; textColor?: string }) {
    const height = 14
    const container = new Container()
    container.position.set(0, this.cellSize - height)

    const name = new Graphics()
    name.fill('#00000080')
    name.rect(0, 0, this.cellSize, height)
    name.fill()

    let textStr = options.name
    if (options.level !== undefined) {
      textStr = 'Lv' + options.level + ' ' + textStr
    }
    const text = new Text({
      text: textStr,
      style: {
        fontSize: 12,
        fill: options.textColor ?? '#FFFFFF',
        align: 'center',
      },
    })
    text.x = this.cellSize / 2
    text.y = height / 2
    text.anchor.set(0.5)

    container.addChild(name, text)
    return container
  }
}
